"""
认证账号相关模型
包含:
1. AuthProviderEnum - 认证源类型枚举
2. UserAuthAccountModel - 用户认证账号模型
3. AuthAccountManager - 认证账号管理器
"""

from datetime import datetime
from enum import IntEnum
import logging
from typing import Optional, Tuple, Dict, Any, List

import bcrypt
from tortoise import fields
from tortoise.transactions import atomic
from tortoise.exceptions import DoesNotExist

from app.core.constants import BoolEnum
from app.models.base_model import BaseModel
from app.models.user_model import UserModel
from app.core.security import get_password_hash, verify_password
from app.utils.timezone import now

logger = logging.getLogger(__name__)


class AuthProviderEnum(IntEnum):
    """认证提供商枚举"""

    EMAIL = 1  # 邮箱
    PHONE = 2  # 手机
    PASSWORD = 3  # 密码
    GITHUB = 4  # GitHub


class UserAuthAccountModel(BaseModel):
    """用户认证账号"""

    user = fields.ForeignKeyField("models.UserModel", related_name="auth_accounts", description="关联用户")
    provider = fields.IntEnumField(AuthProviderEnum, description="认证源类型")
    identifier = fields.CharField(max_length=255, description="认证账号ID")
    credential = fields.CharField(max_length=255, null=True, description="认证凭证")
    raw_data = fields.JSONField(null=True, description="认证源返回的原始数据")

    is_verified = fields.IntEnumField(BoolEnum, default=BoolEnum.FALSE.value, description="是否已验证")
    is_primary = fields.IntEnumField(BoolEnum, default=BoolEnum.FALSE.value, description="是否为主账号")
    bind_time = fields.DatetimeField(auto_now_add=True, description="绑定时间")
    last_used = fields.DatetimeField(description="最后使用时间", null=True)

    class Meta:
        table = "auth_user_account"
        unique_together = (("provider", "identifier"),)  # 同一个认证源的账号ID不能重复

    @staticmethod
    def hash_password(password: str) -> str:
        """密码加密"""
        salt = bcrypt.gensalt()
        return bcrypt.hashpw(password.encode("utf8"), salt).decode("utf8")

    def verify_password(self, password: str) -> bool:
        """验证密码"""
        if not password or not self.credential:
            return False
        return bcrypt.checkpw(password.encode("utf8"), self.credential.encode("utf8"))

    @classmethod
    async def get_by_identifier(cls, provider: AuthProviderEnum, identifier: str) -> Optional["UserAuthAccountModel"]:
        """通过认证源类型和账号ID获取认证账号"""
        try:
            return await cls.get(provider=provider, identifier=identifier)
        except DoesNotExist:
            return None

    @classmethod
    async def get_primary_account(cls, user_id: int, provider: AuthProviderEnum) -> Optional["UserAuthAccountModel"]:
        """获取用户某个认证源的主账号"""
        return await cls.filter(user_id=user_id, provider=provider, is_primary=BoolEnum.TRUE.value).first()

    async def set_as_primary(self) -> None:
        """设置为主账号"""
        # 将同一用户同一认证源的其他账号设置为非主账号
        await self.__class__.filter(
            user_id=self.user_id, provider=self.provider, is_primary=BoolEnum.TRUE.value
        ).update(is_primary=BoolEnum.FALSE.value)

        # 设置当前账号为主账号
        self.is_primary = BoolEnum.TRUE.value
        await self.save()

    async def change_password(self, old_password: str, new_password: str) -> bool:
        """
        修改密码
        :param old_password: 旧密码
        :param new_password: 新密码
        :return: 是否成功
        """
        if not verify_password(old_password, self.credential):
            return False
        self.credential = get_password_hash(new_password)
        await self.save()
        return True


class AuthAccountManager:
    """认证账号管理器"""

    def __init__(self, auth_account: Optional[UserAuthAccountModel] = None):
        self.auth_account = auth_account

    @staticmethod
    async def create_account(
        user: UserModel,
        provider: AuthProviderEnum,
        identifier: str,
        credential: Optional[str] = None,
        raw_data: Optional[Dict[str, Any]] = None,
        is_verified: bool = False,
        is_primary: bool = False,
    ) -> UserAuthAccountModel:
        """
        创建认证账号
        :param user: 用户模型
        :param provider: 认证源类型
        :param identifier: 认证源账号ID
        :param credential: 认证凭证
        :param raw_data: 原始数据
        :param is_verified: 是否已验证
        :param is_primary: 是否为主账号
        :return: 认证账号模型
        """
        # 检查是否已存在相同的认证账号
        if await UserAuthAccountModel.get_by_identifier(provider, identifier):
            raise ValueError(f"Auth account already exists: {provider.name}:{identifier}")

        # 创建认证账号
        auth_account = await UserAuthAccountModel.create(
            user=user,
            provider=provider,
            identifier=identifier,
            credential=credential,
            raw_data=raw_data,
            is_verified=BoolEnum.TRUE.value if is_verified else BoolEnum.FALSE.value,
            is_primary=BoolEnum.TRUE.value if is_primary else BoolEnum.FALSE.value,
        )

        return auth_account

    @classmethod
    @atomic()
    async def register_by_password(cls, username: str, password: str) -> Tuple[UserModel, UserAuthAccountModel]:
        """
        密码注册
        :param username: 用户名
        :param password: 密码
        :return: (用户模型, 认证账号模型)
        """
        # 检查用户名是否已存在
        if await UserModel.filter(username=username).exists():
            raise ValueError("Username already exists")

        # 创建用户
        user = await UserModel.create(username=username)

        # 创建认证账号
        auth_account = await cls.create_account(
            user=user,
            provider=AuthProviderEnum.PASSWORD,
            identifier=username,
            credential=UserAuthAccountModel.hash_password(password),
            is_verified=True,
            is_primary=True,
        )

        return user, auth_account

    @classmethod
    async def login_by_password(cls, username: str, password: str) -> Optional[UserAuthAccountModel]:
        """
        密码登录
        :param username: 用户名
        :param password: 密码
        :return: AuthAccountManager or None
        """
        auth_account = await UserAuthAccountModel.get_by_identifier(AuthProviderEnum.PASSWORD, username)
        if auth_account and auth_account.verify_password(password):
            # 更新最后使用时间
            try:
                auth_account.last_used = now()
                await auth_account.save(update_fields=['last_used'])
            except Exception as e:
                logger.error(f"Failed to update last_used time: {e}", exc_info=True)
            return auth_account
        return None

    @classmethod
    @atomic()
    async def login_by_github(
        cls, github_id: str, github_data: Dict[str, Any], access_token: str
    ) -> Tuple[UserModel, UserAuthAccountModel, bool]:
        """
        GitHub登录
        :param github_id: GitHub用户ID
        :param github_data: GitHub用户数据
        :param access_token: GitHub访问令牌
        :return: (用户模型, 认证账号模型, 是否是新用户)
        """
        # 查找是否已存在GitHub账号
        auth_account = await UserAuthAccountModel.get_by_identifier(AuthProviderEnum.GITHUB, github_id)

        if auth_account:
            # 更新GitHub数据
            auth_account.credential = access_token
            auth_account.raw_data = github_data
            auth_account.last_used = now()
            await auth_account.save()
            return auth_account.user, auth_account, False

        # 创建新用户
        username = f"gh_{github_id}"
        # 确保用户名唯一
        while await UserModel.filter(username=username).exists():
            username = f"gh_{github_id}_{datetime.now().timestamp()}"

        # 从GitHub数据中获取用户信息
        nickname = github_data.get("name") or github_data.get("login", "")
        email = github_data.get("email")
        avatar = github_data.get("avatar_url")

        # 创建用户
        user = await UserModel.create(username=username)

        # 创建用户资料
        await user.profile.create(nickname=nickname, avatar=avatar)

        # 创建GitHub认证账号
        auth_account = await cls.create_account(
            user=user,
            provider=AuthProviderEnum.GITHUB,
            identifier=github_id,
            credential=access_token,
            raw_data=github_data,
            is_verified=True,  # GitHub账号默认已验证
            is_primary=True,  # 设为主账号
        )

        # 如果有邮箱,也创建邮箱认证账号
        if email:
            try:
                await cls.create_account(
                    user=user,
                    provider=AuthProviderEnum.EMAIL,
                    identifier=email,
                    is_verified=True,  # GitHub验证的邮箱可信
                    is_primary=True,
                )
            except ValueError:
                # 邮箱已存在,忽略错误
                pass

        return user, auth_account, True

    @classmethod
    async def bind_github(
        cls, user: UserModel, github_id: str, github_data: Dict[str, Any], access_token: str
    ) -> UserAuthAccountModel:
        """
        绑定GitHub账号
        :param user: 用户模型
        :param github_id: GitHub用户ID
        :param github_data: GitHub用户数据
        :param access_token: GitHub访问令牌
        :return: 认证账号模型
        """
        # 检查是否已绑定GitHub账号
        if await UserAuthAccountModel.get_by_identifier(AuthProviderEnum.GITHUB, github_id):
            raise ValueError("GitHub account already bound")

        # 创建GitHub认证账号
        auth_account = await cls.create_account(
            user=user,
            provider=AuthProviderEnum.GITHUB,
            identifier=github_id,
            credential=access_token,
            raw_data=github_data,
            is_verified=True,
            is_primary=False,  # 绑定时默认不设为主账号
        )

        return auth_account

    @classmethod
    async def bind_account(
        cls,
        user: UserModel,
        provider: AuthProviderEnum,
        identifier: str,
        credential: Optional[str] = None,
        raw_data: Optional[Dict[str, Any]] = None,
        is_verified: bool = False,
        is_primary: bool = False,
    ) -> UserAuthAccountModel:
        """
        绑定账号
        :param user: 用户模型
        :param provider: 认证源类型
        :param identifier: 认证账号ID
        :param credential: 认证凭证
        :param raw_data: 原始数据
        :param is_verified: 是否已验证
        :param is_primary: 是否为主账号
        :return: 认证账号模型
        """
        return await cls.create_account(
            user=user,
            provider=provider,
            identifier=identifier,
            credential=credential,
            raw_data=raw_data,
            is_verified=is_verified,
            is_primary=is_primary,
        )

    async def unbind_account(self) -> None:
        """解绑账号"""
        if not self.auth_account:
            raise ValueError("No auth account")
        await self.auth_account.delete()

    @classmethod
    async def reset_password(cls, username: str, new_password: str) -> bool:
        """
        重置密码
        :param username: 用户名
        :param new_password: 新密码
        :return: 是否成功
        """
        auth_account = await UserAuthAccountModel.get_by_identifier(AuthProviderEnum.PASSWORD, username)
        if not auth_account:
            return False

        auth_account.credential = UserAuthAccountModel.hash_password(new_password)
        await auth_account.save()
        return True

    @staticmethod
    async def get_user_accounts(user_id: int) -> List[UserAuthAccountModel]:
        """
        获取用户的所有认证账号
        :param user_id: 用户ID
        :return: 认证账号列表
        """
        return await UserAuthAccountModel.filter(user_id=user_id).all()

    @staticmethod
    async def get_user_primary_accounts(user_id: int) -> Dict[AuthProviderEnum, UserAuthAccountModel]:
        """
        获取用户的所有主账号
        :param user_id: 用户ID
        :return: {认证源类型: 认证账号}
        """
        accounts = await UserAuthAccountModel.filter(user_id=user_id, is_primary=BoolEnum.TRUE.value).all()
        return {account.provider: account for account in accounts}
